#include "dmnbtext.h"
#include <cmath>
#include <cstdlib>
#include "tts_config.h"
#include <iostream>
using namespace std;



TTSConfig config("config.ini");
string config_path = config.config_path;
//jieba
string dict_path = config_path + "/cppjieba/dict/jieba.dict.utf8";
string hmm_path = config_path + "/cppjieba/dict/hmm_model.utf8";
string user_dict_path = config_path + "/cppjieba/dict/user.dict.utf8";
string idf_path = config_path + "/cppjieba/dict/idf.utf8";
string stop_word_path = config_path + "/cppjieba/dict/stop_words.utf8";

const char* const DICT_PATH = dict_path.c_str();
const char* const HMM_PATH = hmm_path.c_str();
const char* const USER_DICT_PATH = user_dict_path.c_str(); 
const char* const IDF_PATH = idf_path.c_str(); 
const char* const STOP_WORD_PATH = stop_word_path.c_str(); 


cppjieba::Jieba jieba(DICT_PATH,
        HMM_PATH,
        USER_DICT_PATH,
        IDF_PATH,
        STOP_WORD_PATH);

DMNBText::~DMNBText(){
}

DMNBText::DMNBText(){
}

void DMNBText::saveClassifier(string modelFile){
    fstream file(modelFile, ios::out | ios::binary);
    if (!file){
        cout << "Error opening file.";
        return;
    }

    int multinominal = m_multinominal? 1:0;

    file.write(reinterpret_cast<char *>(&multinominal), sizeof(multinominal));
    file.write(reinterpret_cast<char *>(&m_numClass), sizeof(m_numClass));
    file.write(reinterpret_cast<char *>(&m_numWord), sizeof(m_numWord));

    for(map<int,string>::iterator it = m_indexClassMap.begin(); it != m_indexClassMap.end();++it){
        int idx = it->first;
        file.write(reinterpret_cast<char *>(&idx), sizeof(int));
        idx = it->second.size();
        file.write(reinterpret_cast<char *>(&idx), sizeof(int));
        file.write((it->second).c_str(), idx);
    }

    for(map<string,int>::iterator it = m_wordIndexMap.begin(); it != m_wordIndexMap.end();++it){
        int idx = it->second;
        file.write(reinterpret_cast<char *>(&idx), sizeof(int));
        idx = it->first.size();
        file.write(reinterpret_cast<char *>(&idx), sizeof(int));
        file.write((it->first).c_str(), idx);
    }

    for(int i=0; i<m_numClass;i++){
       m_binaryClassifiers[i].saveClassifier(file); 
    }

    file.close();
}

void DMNBText::loadClassifier(string modelFile){
    fstream file(modelFile, ios::in);
    if (!file){
        cout << "Error opening file.";
        return;
    }
    int multinominal;
    file.read(reinterpret_cast<char *>(&multinominal),sizeof(multinominal));
    m_multinominal = multinominal == 1? true:false;
  
    file.read(reinterpret_cast<char *>(&m_numClass), sizeof(m_numClass));
    file.read(reinterpret_cast<char *>(&m_numWord), sizeof(m_numWord));

    int idx,length;
    char text[256];
    m_indexClassMap.clear();
    for(int i=0; i<m_numClass; i++){
        file.read(reinterpret_cast<char *>(&idx), sizeof(int));
        file.read(reinterpret_cast<char *>(&length), sizeof(int));
        file.read(text,length);
        m_indexClassMap[idx] = string(text,length);
    }

    for(int i=0; i<m_numWord; i++){
        file.read(reinterpret_cast<char *>(&idx), sizeof(int));
        file.read(reinterpret_cast<char *>(&length), sizeof(int));
        file.read(text,length);
        m_wordIndexMap[string(text,length)] = idx;
    }

    m_binaryClassifiers = new DNBBinary[m_numClass];
    for(int i=0; i<m_numClass;i++){
       m_binaryClassifiers[i].loadClassifier(file); 
    }

    file.close();
}

void DMNBText::loadTrainFile(string trainFile){
    ifstream infile;
    infile.open(trainFile);
    string s;
	vector<string> txt;

    while(getline(infile,s)){
 		if(!s.empty()){
			txt.push_back(s);
		}
    }
    infile.close();
    
	// 中文分词
    vector<vector<string>> txt_words;
	vector<pair<string, string>> tagres;

	// 词与下标.
	map<string,int> word_count_map;
    map<string,int> class_count_map;

    // "类别\t文本"
    for(int i=0;i<txt.size();i++){
		size_t found = txt[i].find('\t');
		if(found == string::npos){
			cout << "no class: " << txt[i] << endl;
			continue;
		}
		string class_txt = txt[i].substr(0,found);
		string word_txt = txt[i].substr(found+1,string::npos);

		if(class_count_map.find(class_txt) == class_count_map.end()){
			class_count_map[class_txt] = 1;
		}else{
			class_count_map[class_txt] = class_count_map[class_txt] + 1;
		}

		tagres.clear();
    	jieba.Tag(word_txt, tagres);
		vector<string> seg_words;
		seg_words.clear();
		seg_words.push_back(class_txt);
		for(int i=0; i<tagres.size(); i++){
			if(tagres[i].second.substr(0,1) == "w")
                continue;
			string word = tagres[i].first;
			seg_words.push_back(word);
			if(word_count_map.find(word) == word_count_map.end()){
				word_count_map[word] = 1;
			}else{
				word_count_map[word] = word_count_map[word] + 1;
			}
		}
		txt_words.push_back(seg_words);
    }

	// 类别转类下标
	int idx = 0;
	for (std::map<string,int>::iterator it=class_count_map.begin(); it!=class_count_map.end(); ++it){
    	std::cout << "className:" << it->first << "  count:" << it->second << '\n';
		m_classIndexMap[it->first] = idx;
		m_indexClassMap[idx] = it->first;
		idx++;
	}
	m_numClass = idx;

	idx = 0;
	for (std::map<string,int>::iterator it=word_count_map.begin(); it!=word_count_map.end(); ++it){
		m_wordIndexMap[it->first] = idx;
		idx++;
	}

	m_numWord = idx;

	for(int i=0; i< txt_words.size(); i++){
		map<int,int> tmpmap;
		tmpmap.clear();
		int class_index = m_classIndexMap[txt_words[i][0]];
		tmpmap[m_numWord] = class_index; 	//类别标签,作为属性的一部分且类别下标为m_numWord.
		for(int j=1; j < txt_words[i].size();j++){
			string word = txt_words[i][j];
			if(m_wordIndexMap.find(word) == m_wordIndexMap.end()) continue;
			int index = m_wordIndexMap[word];
			if(tmpmap.find(index) == tmpmap.end()){
				tmpmap[index] = 1;
			}else{
				tmpmap[index] = tmpmap[index] + 1;
			}
		}
		m_instances.push_back(tmpmap);
	}
 }

string DMNBText::predict(string text){
    map<int, int> instance;
    makeInstance(text, instance);
    double *prob = new double[m_numClass];
	int classidx = getLogProbForTargetClass(instance, prob);
    char result[256] = {'\0'};
    sprintf(result,"%s:%.3f", m_indexClassMap[classidx].c_str(), prob[classidx]);
    delete [] prob;
    prob = NULL;
    return string(result);
 }

void DMNBText::makeInstance(string text, map<int, int> & instance){
    instance.clear();
    vector<pair<string, string>> tagres;
    tagres.clear();
    instance.clear();
    jieba.Tag(text, tagres);
    for(int i=0; i<tagres.size(); i++){
        if(tagres[i].second.substr(0,1) == "w") continue;
        string word = tagres[i].first;
        map<string,int>::iterator it = m_wordIndexMap.find(word);
        if(it == m_classIndexMap.end()){
            continue;
        }else{
            int index = it->second;
            if(instance.find(index) == instance.end()){
                instance[index] = 1;
            }else{
                instance[index] += 1;
            }
        }
    }
 }

void DMNBText::initClassifier(string trainFile, bool  multiNominal, int numIterations){
    m_multinominal = multiNominal;
    m_numIterations = numIterations;
    loadTrainFile(trainFile);

    //必须在loadTrainFile之后执行. m_numClass,m_numWord由训练文件得出.
    m_binaryClassifiers = new DNBBinary[m_numClass];
    for(int i=0; i<m_numClass; i++){
        //m_binaryClassifiers[i] = new DNBBinary();
        m_binaryClassifiers[i].initClassifier(m_numWord,m_multinominal);
        m_binaryClassifiers[i].setTargetClass(i);
    }
}

void DMNBText::buildClassifier(){
    for (int it = 0; it < m_numIterations * m_instances.size(); it++) {
        int rnd = rand();
        srand(rnd);
        rnd =  rand() % m_instances.size();
        updateClassifier(m_instances[rnd]);
    }
}

void DMNBText::destoryClassifier(){
   if(m_binaryClassifiers){
        delete m_binaryClassifiers;
        m_binaryClassifiers = NULL;
    }
}

void DMNBText::updateClassifier(map<int,int> & instance){
    if (m_numClass == 2) {
        m_binaryClassifiers[0].updateClassifier(instance);
    }else{
        for(int i = 0; i < m_numClass; i++){
            m_binaryClassifiers[i].updateClassifier(instance);
        }
    }
}

int DMNBText::getLogProbForTargetClass(map<int,int> & instance, double * logDocGivenClass){
    if (m_numClass == 2) {
        double ratio=m_binaryClassifiers[0].getLogProbForTargetClass(instance);
        if (ratio > 709)
            logDocGivenClass[0]=1;
        else{
            ratio = exp(ratio);
            logDocGivenClass[0]= ratio / (1 + ratio);
        }
        logDocGivenClass[1] = 1 - logDocGivenClass[0];
        return logDocGivenClass[0] > logDocGivenClass[1]? 0:1;
    }
  
    //cout << "-------------origin-------------------" << endl;
    for (int i = 0; i < m_numClass; i++){
      logDocGivenClass[i] = m_binaryClassifiers[i].getLogProbForTargetClass(instance);
      //cout << "logDocGivenClass[" << i << "] = " << logDocGivenClass[i] << endl;
    }

    int max_index = 0;
    double max_value = logDocGivenClass[0];
    for(int i=1; i < m_numClass; i++){
        if(logDocGivenClass[i] > max_value){
            max_index =  i;
            max_value = logDocGivenClass[i];
        }
    }

    double sum_value = 0;
    //cout << "-------------exp-------------------" << endl;
    for(int i = 0; i<m_numClass; i++){
        logDocGivenClass[i] = exp(logDocGivenClass[i] - max_value);
        //cout << "logDocGivenClass[" << i << "] = " << logDocGivenClass[i] << endl;
        sum_value += logDocGivenClass[i];
    }

    // cout << "sum_value = " << sum_value << endl; 

    //cout << "-------------divide sum-------------------" << endl;
    for(int i = 0; i<m_numClass; i++){
        logDocGivenClass[i] /= sum_value;
        //cout << "logDocGivenClass[" << i << "] = " << logDocGivenClass[i] << endl;
    }
    return max_index;
}
